//
//  DNSHelper.m
//  hqedu24olapp
//
//  Created by ouyongyong on 16/8/4.
//  Copyright © 2016年 edu24ol. All rights reserved.
//

#import "DNSHelper.h"
#import <CommonCrypto/CommonDigest.h>
#import "RetryAPI.h"
#import <AFHTTPSessionManager.h>
#import <YTKNetworkConfig.h>
#import "MJExtension.h"
#import "ReactiveCocoa/RACEXTScope.h"
#import "HQBaseRequestSDK.h"
#import "HQResponseStatus.h"
#import "DNSHelperSetting.h"

static const int kMd5BufferLength = 16;
static const int kSHa1BufferLength = 20;

#define DNSHelper_APP_VERSION ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"])

static NSString * const kDnsServBaseReq = @"http://{IP}/host2allip?";

static NSString * const kDNSPoolKey   =   @"com.edu24ol.globalNetworkClassroom.DNSHelper.dnsCache";

static const NSTimeInterval badHostLifeTime = 60*30;//半小时应急方案时间

@interface DNSHelper ()

@property (nonatomic, strong) NSDictionary <NSString *, NSArray *> *dnsCache;//缓存
@property (nonatomic, strong) NSMutableDictionary <NSString *, NSMutableArray *> *dnsPool; //内存

@property (nonatomic, strong) AFHTTPSessionManager *networkManager;

@property (atomic, assign) BOOL isRequestingDns;

@property (nonatomic, strong) NSMutableArray *waitingRequests;

//记录被污染的host什么时候恢复正常流程
@property (nonatomic, strong) NSMutableDictionary *badHostLifeMutableDict;
// 记录不可用的HOST
@property (nonatomic, strong) NSMutableSet *badMutableHosts;

@end


@implementation DNSHelper

+ (DNSHelper *)shareInstance
{
    static DNSHelper* helper;
    static dispatch_once_t token;
    dispatch_once(&token, ^{
        helper = [[DNSHelper alloc] init];
    });
    
    return helper;
}

/// 随机选取并设置ip
- (NSURL *)buildReq
{
    NSInteger index = arc4random()%[self dnsServers].count;
    NSString *ip = [[self dnsServers] objectAtIndex:index];
    
    return [NSURL URLWithString:[kDnsServBaseReq stringByReplacingOccurrencesOfString:@"{IP}" withString:ip]];
}

- (NSDictionary *)buildParamsWithHosts:(NSArray <NSString *> *)hosts uid:(uint64_t)uid
{
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:6];
    
    long _t = (long)([NSDate date].timeIntervalSince1970*1000);
    NSString *hostParams = [self stringFromHosts:hosts];
    
    dict[@"_t"] = @(_t);
    dict[@"_p"] = hostParams;
    dict[@"_v"] = [self versionStr];
    dict[@"_appid"] = @(100001);
    dict[@"_u"] = @(uid);
    
    dict[@"_auth"] = [[self class] stringForMD5:[NSString stringWithFormat:@"%@|%ld", hostParams, _t]];
    
    return dict;
}
 
- (NSString *)stringFromHosts:(NSArray <NSString *> *)hosts
{
    if (!hosts || hosts.count == 0) {
         return @"";
    }
    
    NSMutableString *str = @"".mutableCopy;
    
    for (NSString* host in hosts) {
        [str appendString:host];
        if (host != hosts.lastObject) {
             [str appendString:@","];
        }
    }
    
    return str;
}

- (NSString *)versionStr
{
    return DNSHelper_APP_VERSION?DNSHelper_APP_VERSION:@"";
}

- (NSArray *)dnsServers
{
    return @[@"netcgi.98809.com"];
    ///下面的ip总是有问题
    return @[@"netcgi.98809.com",
             @"47.115.36.183:8080",
             @"47.93.26.1:8080"];
}

- (AFHTTPSessionManager *)networkManager
{
    if (!_networkManager) {
        _networkManager = [AFHTTPSessionManager manager];
    }
    return _networkManager;
}

- (NSArray *)safeGetIpListForHost:(NSString *)hostName
{
    if (!hostName) {
         return nil;
    }
    
    id arr = self.dnsPool[hostName];
    if ([arr isKindOfClass:[NSArray class]]) {
        return arr;
    }
    return nil;
}


#pragma mark - private methods
- (void)queryIPForHost:(NSString *)host withCompletionBlock:(void(^)(BOOL succ))block
{
    if (!host) {
        if (block) {
             block(NO);
        }
        return;
    }
    
    self.isRequestingDns = YES;
    
    NSURL *URL = [self buildReq];
    
    AFHTTPSessionManager *manager = self.networkManager;
    
    NSMutableSet *types = manager.responseSerializer.acceptableContentTypes.mutableCopy;
    [types addObject:@"text/plain"];
    
    manager.responseSerializer.acceptableContentTypes = types;
    
    NSDictionary *params = [self buildParamsWithHosts:@[host] uid:[self.hq_userId longLongValue]];
    
    
    [manager GET:URL.absoluteString parameters:params success:^(NSURLSessionTask *task, id responseObject) {
        
        @try {
            if (![responseObject isKindOfClass:[NSDictionary class]]) {
                DDLogInfo(@"<DNSHelper>invalid host info");
                if (block) {
                     block(NO);
                }
                return ;
            }
            self.isRequestingDns = NO;
            NSDictionary *jsonDict = responseObject;
            NSArray *arr = jsonDict[@"hosts"];
            
            if (!arr || ![arr isKindOfClass:[NSArray class]]) {
                DDLogInfo(@"<DNSHelper>no host info");
                if (block) {
                     block(NO);
                }
                return ;
            }
            NSDictionary *dict = arr.firstObject;
            if (![dict isKindOfClass:[NSDictionary class]]) {
                DDLogInfo(@"<DNSHelper>invalid host info");
                if (block) {
                     block(NO);
                }
                return;
            }
            
            NSString *hostName = dict[@"host"];
            NSArray *iplist = dict[@"iplist"];
            if ([host isEqualToString:hostName] && [iplist isKindOfClass:[NSArray class]] && iplist.count > 0) {
                @synchronized (self.dnsPool) {
                    [self.dnsPool setObject:[[NSMutableArray alloc] initWithArray:iplist] forKey:hostName];
                    self.dnsCache = self.dnsPool;
                }
                if (block) {
                     block(YES);
                }
            } else {
                DDLogInfo(@"<DNSHelper> bad dns return host %@ iplist %@", hostName, iplist);
                if (block) {
                     block(NO);
                }
            }
        } @catch (NSException *exception) {
            DDLogError(@"<DNSHelper> catch exp %@",exception);
        }
        
    } failure:^(NSURLSessionTask *operation, NSError *error) {
        DDLogInfo(@"<DNSHelper>updateIpList fail: %@", @(error.code));
        self.isRequestingDns = NO;
        if (block) {
             block(NO);
        }
    }];
}

/// 第一步、判断能否使用dns解析
- (BOOL)shouldUseIpForRequest:(YTKRequest *)request
{
    NSString *host = [self hostForRequest:request];
    if (![request isKindOfClass:[RetryAPI class]] && [self isBadHost:request] && ![self isBadHostOutDate:host]) {
        DDLogInfo(@"<DNSHelper>bad host %@, use ip ", host);
        return YES;
    }
    return NO;
}

- (BOOL)shouldRetryWithIpForRequest:(YTKRequest *)request
{
    return ([self isNetWorkAvalible]) && [self isDNSpollution:request];
    
}

- (BOOL)isNetWorkAvalible
{
    if (![AFNetworkReachabilityManager sharedManager].isReachable) {
        DDLogInfo(@"<DNSHelper> 网络连接失败啦~");
        return NO;
    }
    return YES;
}

+ (BOOL)isDNSError:(NSInteger)errorCode
{
    if (![AFNetworkReachabilityManager sharedManager].isReachable) {
         return NO;
    }
    
    return (errorCode >= 500 && errorCode < 599) || (errorCode == 104) || (errorCode == 0) || (errorCode == 403);
}

- (BOOL)isDNSpollution:(YTKRequest *)request
{
    NSInteger code = request.responseStatusCode;
    BOOL res =  (request.responseStatusCode >= 500 && request.responseStatusCode < 599) || (request.responseStatusCode == 104) || (request.responseStatusCode == 0) || (request.responseStatusCode == 403);
    DDLogInfo(@"<DNSHelper> %@ dns pollution (code=%ld-%ld)", res?@"":@"NOT", code,request.requestTask.error.code);
    return res;
}

- (NSString *)hostForRequest:(YTKRequest *)request
{
    NSString *detailUrl = [YTKNetworkConfig sharedConfig].baseUrl;
    if ([request.requestUrl hasPrefix:@"http"]) {
        detailUrl = request.requestUrl;
    }
    
    return [NSURL URLWithString:detailUrl].host;
}

- (BOOL)isBadHost:(YTKRequest *)request
{
    NSString *host = [self hostForRequest:request];
    if (!host) {
         return YES;
    }
    
    BOOL result = [self.badMutableHosts containsObject:host];
    if ([host containsString:@".com"] && !result) {
         DDLogDebug(@"<DNSHelper> %@ is od", host);
    } else
        DDLogInfo(@"<DNSHelper> %@ is %@ bad", host, result?@"":@"NOT");
    
    return result;
}

- (BOOL)isBadHostOutDate:(NSString *)badHost
{
    if (!badHost) {
         return YES;
    }
    
    NSNumber *time = self.badHostLifeMutableDict[badHost];
    long now = [[NSDate date] timeIntervalSince1970];
    if (time.longValue < now) {
        [self noMoreBadHost:badHost];
        return YES;
    }
    return NO;
}

- (void)noMoreBadHost:(NSString *)host
{
    if (!host) {
         return;
    }
    
    DDLogInfo(@"<DNSHelper> no more bad host %@", host);
    @synchronized (self.badMutableHosts) {
        [self.badMutableHosts removeObject:host];
        [self.badHostLifeMutableDict removeObjectForKey:host];
    }
}

- (void)insertBadHost:(NSString *)host
{
    if (!host) {
         return;
    }
    DDLogInfo(@"<DNSHelper>insert bad host %@", host);
    @synchronized (self.badMutableHosts) {
        [self.badMutableHosts addObject:host];
        long now = [[NSDate date] timeIntervalSince1970];
        [self.badHostLifeMutableDict setObject:@(now + badHostLifeTime) forKey:host];
    }

}

- (NSString *)ipForHost:(NSString *)hostName
{
    @synchronized (self.dnsPool) {
        NSArray *ipList = [self safeGetIpListForHost:hostName];
        if (!ipList || ipList.count == 0) {
             return nil;
        }
        DDLogInfo(@"ipList:%ld",ipList.count);
        for (id ip in ipList) {
            if (![self isValidIp:ip]) {
                [self removeInvalidIp:ip forHost:hostName];
                continue;
            }
            DDLogInfo(@"<DNSHelper> %@ -> %@", hostName, ip);
            return ip;
        }
        
        return nil;
    }
}

- (NSString *)cacheIpForHost:(NSString *)hostName
{
    NSArray *ipList = self.dnsCache[hostName];
    if (!ipList || ![ipList isKindOfClass:[NSArray class]] || ipList.count == 0) {
//        NSString * name = @"app_iplist_20161212";
        NSString * name = @"app_iplist_20211201";
        NSString *filePath = [[NSBundle mainBundle] pathForResource:name ofType:@"json"];
        if (!filePath) {
            filePath = [[self class] filePathForm:name];
        }
      
        NSData *data = [NSData dataWithContentsOfFile:filePath];
        NSDictionary *dict = [data mj_JSONObject];
        NSArray *arr = dict[@"hosts"];
        for (NSDictionary* ipDict in arr) {
            NSString *host = ipDict[@"host"];
            if ([host isEqualToString:hostName]) {
                NSArray *ipList = ipDict[@"iplist"];
                NSString *ip = [ipList objectAtIndex:(arc4random()%ipList.count)];
                if ([self isValidIp:ip]) {
                    return ip;
                }
            }
        }
        return nil;
    } else {
        NSString *ip = [ipList objectAtIndex:(arc4random()%ipList.count)];
        return ip;
    }
    return nil;
}

+ (NSString *)filePathForm:(NSString *)fileName
{
    NSString * bundleName = @"HqAppArchitect";
    NSBundle *mainBundle = [NSBundle mainBundle];
    NSURL *bundleURL = [mainBundle URLForResource:@"HqAppArchitect" withExtension:@"bundle"];
    NSString * path = @"";
    if (bundleURL) {
        NSBundle *bundle = [NSBundle bundleWithURL:bundleURL];
        path = [bundle pathForResource:fileName ofType:@"json"];
    }
    
    if (!path) {
        // 看看是否使用了user_frameworks
        NSURL *associateBundleURL = [mainBundle URLForResource:@"Frameworks" withExtension:nil];
        associateBundleURL = [associateBundleURL URLByAppendingPathComponent:bundleName];
        associateBundleURL = [associateBundleURL URLByAppendingPathExtension:@"framework"];
        
        NSBundle *associateBunle = [NSBundle bundleWithURL:associateBundleURL];
        associateBundleURL = [associateBunle URLForResource:bundleName withExtension:@"bundle"];
        if (associateBundleURL) {
            NSBundle *bundle = [NSBundle bundleWithURL:associateBundleURL];
            path = [bundle pathForResource:fileName ofType:@"json"];
        }
        
    }
    
    return path;
}

//移除不合法IP
- (void)removeInvalidIp:(id)ip forHost:(NSString *)host
{
    @synchronized (self.dnsPool) {
        [self.dnsPool[host] removeObject:ip];
        self.dnsCache = self.dnsPool;
    }
}

//失效的IP放到队尾
- (void)failIp:(id)ip forHost:(NSString *)host
{
    @synchronized (self.dnsPool) {
        DDLogInfo(@"bad ip %@ for host %@", ip, host);
        NSMutableArray *arr = self.dnsPool[host];
        if (arr && ip) {
            [arr removeObject:ip];
            [arr addObject:ip];
        }
    }
}

- (BOOL)isValidIp:(id)ip
{
    if (!ip) {
         return NO;
    }
    
    if (![ip isKindOfClass:[NSString class]]) {
        DDLogInfo(@"<DNSHelper> invalid ip %@", ip);
        return NO;
    }
    //TODO:细化IP检验规则,包括IPV4和IPV6
    return YES;
}


#pragma mark - properties
- (NSMutableDictionary <NSString *, NSMutableArray *> *)dnsPool
{
    if (!_dnsPool) {
        _dnsPool = [[NSMutableDictionary alloc] init];
    }
    return _dnsPool;
}

- (void)setDnsCache:(NSDictionary<NSString *,NSArray *> *)dnsCache
{
    if (!dnsCache) {
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:kDNSPoolKey];
    } else {
        NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dnsCache];
        if (data) {
             [[NSUserDefaults standardUserDefaults] setObject:data forKey:kDNSPoolKey];
        }
    }
    
    [[NSUserDefaults standardUserDefaults] synchronize];
}

- (NSDictionary <NSString *, NSArray *> *)dnsCache
{
    NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:kDNSPoolKey];
    if (data) {
        NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithData:data];
        return dict;
    }
    return @{};
}

- (NSMutableArray *)waitingRequests
{
    if (!_waitingRequests) {
        _waitingRequests = [[NSMutableArray alloc] init];
    }
    return _waitingRequests;
}

- (NSMutableDictionary *)badHostLifeMutableDict
{
    if (!_badHostLifeMutableDict) {
        _badHostLifeMutableDict = [[NSMutableDictionary alloc] init];
    }
    return _badHostLifeMutableDict;
}

- (NSDictionary *)badHostLifeDict
{
    return [self.badHostLifeMutableDict copy];
}

- (NSMutableSet *)badMutableHosts
{
    if (!_badMutableHosts) {
        _badMutableHosts = [[NSMutableSet alloc] init];
    }
    return _badMutableHosts;
}

- (NSSet *)badHosts
{
    return [self.badMutableHosts copy];
}

#pragma mark - public method
- (void)handleFailRequest:(YTKRequest *)request
              withSuccess:(void (^)(YTKBaseRequest *request))success
                  failure:(void (^)(YTKBaseRequest *request))failure
{
    NSInteger code = request.requestTask.error.code;
       if ((code == kCFURLErrorTimedOut)||(code == kCFURLErrorNotConnectedToInternet)|| (code == kCFURLErrorCancelled) || (code == kCFURLErrorFileDoesNotExist)) {
        DDLogInfo(@"<DNSHelper> no need retry,error.code = %ld",code);
        [self.badMutableHosts removeAllObjects];
        [self.badHostLifeMutableDict removeAllObjects];
        if (failure) {
             failure(request);
        }
        return;
    }
    
    if (![self shouldRetryWithIpForRequest:request]) {
        DDLogInfo(@"<DNSHelper> no need retry");
        if (failure) {
             failure(request);
        }
        return;
    }
  
    
    if ([request isKindOfClass:[RetryAPI class]]) {
        NSString *ip = ((RetryAPI *)request).tryIp;
        [self failIp:ip forHost:((RetryAPI *)request).originHost];
    } else {
        [self insertBadHost:[self hostForRequest:request]];
    }
    
    [self dnsRetryWithRequest:request success:success failure:failure];
    
}

/// 第二步、如果可以则使用dns解析
- (void)dnsRetryWithRequest:(YTKRequest *)request
                    success:(void (^)(YTKBaseRequest *request))success
                    failure:(void (^)(YTKBaseRequest *request))failure
{
    RetryAPI *api = [[RetryAPI alloc] initWithRequest:request];
    
    api.failBlock = failure;
    
    @weakify(self)
    void (^successBlock)(HQBaseRequestSDK *request)  = ^(HQBaseRequestSDK *request) {
        @strongify(self)
        NSDictionary *json = request.responseObject;
        
        @try {
            if ([self usingSimpleStatus:json]) {
                //非标准的返回状态留给上层处理
                
                if (success) {
                    success(request);
                }
                return ;
            }
            
            if (json && [json isKindOfClass:[NSDictionary class]] && json[@"status"]) {
                //获取状态
                HQResponseStatus *status = [HQResponseStatus mj_objectWithKeyValues:json[@"status"]];
                //状态判断 如果状态为0，则表示成功
                if ([request isKindOfClass:[RetryAPI class]]) {
                    DDLogInfo(@"<%@>dns retry succ", [request class]);
                }
                
                __block BOOL isSuccess = NO;
                NSSet *successCodes = [NSSet setWithObjects:@(0), nil];
                if ([request respondsToSelector:@selector(ErrorCode_HaveNoData)]) {
                    successCodes = [NSSet setWithObjects:@(0),@([request ErrorCode_HaveNoData]), nil];
                }
                [successCodes enumerateObjectsUsingBlock:^(id  _Nonnull obj, BOOL * _Nonnull stop) {
                    if (status.code == [obj integerValue]) {
                        isSuccess = YES;
                        *stop = YES;
                    }
                }];
                
                if (isSuccess) {
                    if (success) {
                        success(request);
                    }
                   
                } else {
                    if (failure) {
                        failure(request);
                    }
                }
            } else {
                if (failure) {
                    failure(request);
                }
                DDLogError(@"<%@> req(%@) Error return data format:line-516 %@",[request class], request.currentRequest.URL, json.description);
            }
            DDLogDebug(@"request %@ finish", request.currentRequest);
        } @catch (NSException *exception) {
            DDLogError(@"<%@> exp %@, req(%@), resp(%@)",[request class], exception, request.currentRequest.URL, json.description);
        }
        
    };
    
    api.succBlock = successBlock;
    
    if (api.retryTime > 6) {
        DDLogInfo(@"<DNSHelper> retry maximum time");
        if (failure) {
             failure(request);
        }
        return;
    }
    
    if (self.isRequestingDns) {
        @synchronized (self.waitingRequests) {
            [self.waitingRequests addObject:api];
        }
    } else {
        NSString *ip = [self ipForHost:api.originHost];
        
        if (!ip) {
            @synchronized (self.waitingRequests) {
                [self.waitingRequests addObject:api];
            }
            
            @weakify(self)
            [self queryIPForHost:api.originHost withCompletionBlock:^(BOOL succ) {
                @strongify(self)
                [self runWaitingQueue];
            }];
            return;
        }
        //ip 已经重试过了;return
        if ([api isRetryedForIp:ip]) {
            if (failure) {
                 failure(request);
            }
            return;
        }
        [api startReqWithIp:ip];
    }
}

- (void)runWaitingQueue
{
    @synchronized (self.waitingRequests) {
        NSArray *waitingReqArray = self.waitingRequests.copy;
        for (RetryAPI* api in waitingReqArray) {
            NSString *ip = [self ipForHost:api.originHost];
            if (!ip || ip.length == 0) {
                ip = [self cacheIpForHost:api.originHost];
            }
            //ip 已经重试过了;return
            if ([api isRetryedForIp:ip]) {
                DDLogInfo(@"runWaitingQueue: ip:%@ retryed for host %@",ip, api.originHost);
                if (api.failBlock) {
                    api.failBlock(api);
                }

                continue;
            }

            if ([self isValidIp:ip]) {
                [api startReqWithIp:ip];
            } else {
                DDLogInfo(@"no ip for host %@", api.originHost);
                if (api.failBlock) {
                    api.failBlock(api);
                }

            }
        }
        [self.waitingRequests removeAllObjects];
    }
}

/*
 以下两类返回没有status详情
 {
 "status": 1,
	"data": { }
 }
 
 {  "code": 1,
	"msg": 99437
 }
 */
- (BOOL)usingSimpleStatus:(NSDictionary *)respDict
{
    id status = respDict[@"status"];
    
    if (status && [status isKindOfClass:[NSDictionary class]]) {
         return NO;
    }
    
    if (!status) {
         status = respDict[@"code"];
    }
    
    return (status == nil);
}

#pragma mark-- 字符串处理
+ (NSString *)stringForMD5:(NSString *)str
{
    NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
    NSData *md5data = [[self class] dataForMD5:data];
    return [[self class] hexString:md5data];
}

+ (NSString *)hexString:(NSData *)data
{
    const int length = (int)[data length];
    const unsigned char *str = [data bytes];
    NSMutableString *hexStr = [NSMutableString stringWithCapacity:length*2];
    for (int i=0; i<length; i++) {
        [hexStr appendFormat:@"%02x", str[i]];
    }
    return hexStr;
}

+ (NSData *)dataForMD5:(NSData *)data
{
    const char *str = [data bytes];
    unsigned char digest[kMd5BufferLength];
    CC_MD5(str, (int)[data length], digest);
    return [NSData dataWithBytes:digest length:kMd5BufferLength];
}

+ (NSData *)dataForSHA1:(NSData *)data
{
    const unsigned char *buffer = [data bytes];
    unsigned char result[kSHa1BufferLength];
    CC_SHA1(buffer, (int)[data length], result);
    return [NSData dataWithBytes:result length:kSHa1BufferLength];
}

 



@end
